﻿namespace EFS.RC.CC.Sessions.OfficeService.Operators
{
	/// <summary>
	/// Operator dial out session
	/// </summary>
	internal class DialOut : EFS.RC.CC.Sessions.OfficeService.Operators.Operator
	{
		#region Composition

		/// Office conversation
		private Microsoft.Rtc.Collaboration.Conversation officeConversation;

		// Customer call
		private Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall inboundAgentAudioVideoCall = null;

		// office call
		private Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall officeConversationAudioVideoCall = null;

		// back to back call into office conversation
		private Microsoft.Rtc.Collaboration.BackToBackCall officeConversationB2BCall;

		// Impersonated display name
		private System.String impersonatedDisplayName = null;

		#endregion

		#region Constructor

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="conversation"></param>
		/// <param name="remoteEndpoint"></param>
		public DialOut(
			EFS.RC.CC.Presences.Operators.Presence operatorPresence, 
			EFS.RC.CC.Applications.EndPoints.OfficeService.Customers customersOfficeServiceEndPointApplication, 
			Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall inboundAgentAudioVideoCall,
			Microsoft.Rtc.Signaling.FromToHeader fromHeader,
			Microsoft.Rtc.Signaling.FromToHeader toHeader)
			: base(operatorPresence, new EFS.RC.CC.Sessions.OfficeService.Customers.DialOut(customersOfficeServiceEndPointApplication, fromHeader, toHeader))
		{
			// check
			EFS.Common.Verify.Reference(inboundAgentAudioVideoCall, "inboundAgentAudioVideoCall");

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "DialOut:ctor", OperatorPresence.User.SIP, GetType());

			// make display name
			if (EFS.Common.Verify.String(toHeader.DisplayName))
			{
				impersonatedDisplayName = toHeader.DisplayName;
			}
			else
			{
				impersonatedDisplayName = "";
			}

			// set
			this.inboundAgentAudioVideoCall = inboundAgentAudioVideoCall;
			this.inboundAgentAudioVideoCall.Conversation.ApplicationContext = this;

			// impersonate
			inboundAgentAudioVideoCall.Conversation.Impersonate(toHeader.Uri, null, impersonatedDisplayName);
		}

		#endregion

		#region Usage

		/// <summary>
		/// Describe this object into a nice formated string
		/// </summary>
		/// <returns></returns>
		public override System.String Describe()
		{
			return System.String.Format(System.String.Format("EFS.RC.CC.Sessions.OfficeService.Operators.DialOut [{0}] [{1}]", OperatorPresence.Describe(), CustomerSession.Describe()));
		}

		/// <summary>
		/// customer dial out session
		/// </summary>
		public EFS.RC.CC.Sessions.OfficeService.Customers.DialOut CustomerDialOutSession
		{
			get
			{
				// cast up
				EFS.RC.CC.Sessions.OfficeService.Customers.DialOut customerDialOutSession = CustomerSession as EFS.RC.CC.Sessions.OfficeService.Customers.DialOut;
				EFS.Common.Verify.Reference(customerDialOutSession, "customerDialOutSession");

				// get
				return customerDialOutSession;
			}
		}

		/// <summary>
		/// Handle incomming call from conference
		/// </summary>
		/// <param name="call"></param>
		internal void HandleReplacedCall(Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall transferredAudioVideoCall)
		{
			// check
			EFS.Common.Verify.Reference(transferredAudioVideoCall, "transferredAudioVideoCall");

			// check not already handled
			// ~ commented for performance ~ EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check
				if (Status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Dial In Session is not Started");
				}

				// get app
				EFS.RC.CC.Applications.EndPoints.OfficeService.Operators operatorsOfficeServiceEndPointApplication = CustomerSession.
					CustomersOfficeServiceEndPointApplication.
					ApplicationPresence.
					EndPointsPresencesRegistrar.
					OperatorOfficeServiceEndPointPresence.
					PresenceEndPointApplication;

				// get agent for an external invite
				EFS.RC.CC.Presences.Operators.Presence presence = operatorsOfficeServiceEndPointApplication.OperatorsTeam.GetAgentBySIP(transferredAudioVideoCall.RemoteEndpoint.Participant.Uri);

				// check
				if (null == presence)
				{
					// make anon operator
					presence = new EFS.RC.CC.Presences.Operators.Anonymous(transferredAudioVideoCall.RemoteEndpoint.Participant.Uri);
				}

				// make a session
				EFS.RC.CC.Sessions.OfficeService.Operators.Transfer operatorTransferSession = new EFS.RC.CC.Sessions.OfficeService.Operators.Transfer(presence, CustomerSession, transferredAudioVideoCall);
				operatorTransferSession.AsinkEstablishedDelegate.Register(OnOperatorTransferSessionEstablished);

				// start agent session
				operatorTransferSession.BeginStartup();
			}
		}

		#endregion

		#region Startup

		/// <summary>
		/// Startup
		/// </summary>
		protected override void SetupSession()
		{
			// log
			EFS.EventLog.Log.LogInfo("SetupSession", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "SetupSession", this); 

			// register for event
			CustomerDialOutSession.OfficeServiceConference.AsinkEstablishedDelegate.Register(OnOfficeServiceConferenceEstablished);

			// register for established
			CustomerDialOutSession.AsinkEstablishedDelegate.Register(OnCustomerDialOutSessionEstablished);

			// start customer session
			CustomerDialOutSession.BeginStartup();
		}

		/// <summary>
		/// Delegate
		/// </summary>
		/// <param name="customerDialOutSession"></param>
		private void OnCustomerDialOutSessionEstablished(EFS.RC.CC.Sessions.OfficeService.IParticipant officeServiceSessionParticipant)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnCustomerSessionTerminated", this);

			// check
			EFS.Common.Verify.Reference(officeServiceSessionParticipant, "officeServiceSessionParticipant");

			// cast
			EFS.RC.CC.Sessions.OfficeService.Customers.Customer customerSession = officeServiceSessionParticipant as EFS.RC.CC.Sessions.OfficeService.Customers.Customer;

			// check
			EFS.Common.Verify.Reference(customerSession, "customerSession");

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check customer session
				if (!this.OfficeServiceConference.CustomerSession.Equals(customerSession))
				{
					throw new System.InvalidOperationException("OnCustomerSessionTerminated CustomerSession is different");
				}
			}

			// stop music
			BeginStopPlayer();
		}

		/// <summary>
		/// Office service conference established
		/// </summary>
		/// <param name="agentHunter"></param>
		private void OnOfficeServiceConferenceEstablished(EFS.RC.CC.Confereces.OfficeService officeServiceConference)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnOfficeServiceConferenceEstablished", this);

			// check
			EFS.Common.Verify.Reference(officeServiceConference, "officeServiceConference");

			// check mine
			EFS.Common.Verify.Condition(CustomerSession.OfficeServiceConference.Equals(officeServiceConference), "Established conference is different");

			// register opertor session
			CustomerSession.OfficeServiceConference.SetOperatorSession(this);

			// accept call
			BeginAsyncProcess2(JoinOfficeConference);
		}

		/// <summary>
		/// Make back2back call for customer into conference
		/// </summary>
		private void JoinOfficeConference()
		{
			// log
			EFS.EventLog.Log.LogInfo("JoinOfficeConference", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerDialOutSession.ConversationGuid, "JoinOfficeConference", this);

			// get conference uri
			System.String conferenceUri = OfficeServiceConference.ConferenceUri;

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", this);
			lock (syncAccess)
			{
				// check status
				if (Status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// log impersonation
				EFS.EventLog.Log.LogInfo(System.String.Format("Impersonate officeConversation {0}", ParticipantEndPointUri), this);

				// make office conversation
				officeConversation = new Microsoft.Rtc.Collaboration.Conversation(inboundAgentAudioVideoCall.Conversation.Endpoint);
				officeConversation.Impersonate(ParticipantEndPointUri, null, CustomerSession.DisplayName);
				officeConversation.ApplicationContext = this;
				officeConversation.StateChanged += OnOfficeConversationStateChanged;

				// conference options
				Microsoft.Rtc.Collaboration.ConferenceJoinOptions conferenceJoinOptions = new Microsoft.Rtc.Collaboration.ConferenceJoinOptions();
				conferenceJoinOptions.JoinMode = Microsoft.Rtc.Collaboration.JoinMode.Default;

				// join
				officeConversation.ConferenceSession.BeginJoin(
					conferenceUri,
					conferenceJoinOptions,
					EFS.Common.Async.AsyncResult.StateDelegate.End,
					EFS.Common.Async.AsyncResult.StateDelegate.State(EndJoinOfficeConference, null));
			}
		}

		/// <summary>
		/// End join
		/// </summary>
		/// <param name="result"></param>
		private void EndJoinOfficeConference(System.IAsyncResult ar)
		{
			EFS.EventLog.Log.LogInfo("EndJoinOfficeConference", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerDialOutSession.ConversationGuid, "EndJoinOfficeConference", this);

			try
			{
				// end join
				officeConversation.ConferenceSession.EndJoin(ar);

				// make back2back call
				BeginAsyncProcess2(MakeB2BCall);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

		/// <summary>
		/// Make back2back call for customer into conference
		/// </summary>
		private void MakeB2BCall()
		{
			// log
			EFS.EventLog.Log.LogInfo("MakeB2BCall", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerDialOutSession.ConversationGuid, "MakeB2BCall", this);

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", this);
			lock (syncAccess)
			{
				// check status
				if (Status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// Make B2B
				officeConversationAudioVideoCall = new Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall(officeConversation);

				// handle transfer
				inboundAgentAudioVideoCall.TransferReceived += OnAudioVideoCallTransferReceived;

				// make call
				officeConversationB2BCall = new Microsoft.Rtc.Collaboration.BackToBackCall(
						new Microsoft.Rtc.Collaboration.BackToBackCallSettings(inboundAgentAudioVideoCall),
						new Microsoft.Rtc.Collaboration.BackToBackCallSettings(officeConversationAudioVideoCall));

				// start call
				officeConversationB2BCall.BeginEstablish(
					EFS.Common.Async.AsyncResult.StateDelegate.End,
					EFS.Common.Async.AsyncResult.StateDelegate.State(OnEndEstablishB2BCall));
			}
		}

		/// <summary>
		/// End B2B call
		/// </summary>
		/// <param name="ar"></param>
		private void OnEndEstablishB2BCall(System.IAsyncResult ar)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnEndEstablishB2BCall", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerDialOutSession.ConversationGuid, "OnEndEstablishB2BCall", this);

			try
			{
				// end
				officeConversationB2BCall.EndEstablish(ar);

				// established
				AsinkEstablishedDelegate.Start();
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

		#endregion

		#region Runtime

		/// <summary>
		/// Transfer DDO
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void OnAudioVideoCallTransferReceived(object sender, Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCallTransferReceivedEventArgs e)
		{
			try
			{
				// make tranfer 
				EFS.RC.CC.Tools.Functions.Operator.Trasfer transfer = new EFS.RC.CC.Tools.Functions.Operator.Trasfer(this, e.TransferDestination);

				// start transfer
				transfer.BeginTransfer();

				// accept
				e.Accept(null);
			}
			catch (System.Exception ex)
			{
				// accept
				e.Decline();

				// log
				EFS.EventLog.Log.LogException(ex, this);
			}
		}

		/// <summary>
		/// Agent session established
		/// </summary>
		private void OnOperatorTransferSessionEstablished(EFS.RC.CC.Sessions.OfficeService.IParticipant officeServiceSessionParticipant)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnSessionTerminated", this);

			// check
			EFS.Common.Verify.Reference(officeServiceSessionParticipant, "officeServiceSessionParticipant");

			// cast
			EFS.RC.CC.Sessions.OfficeService.Operators.Operator operatorSession = officeServiceSessionParticipant as EFS.RC.CC.Sessions.OfficeService.Operators.Operator;

			// check
			EFS.Common.Verify.Reference(operatorSession, "operatorSession");

			// sync
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());

			// shut down
			BeginShutdown();
		}

		/// <summary>
		/// State changed for office conersation
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="args"></param>
		private void OnOfficeConversationStateChanged(object sender, Microsoft.Rtc.Signaling.StateChangedEventArgs<Microsoft.Rtc.Collaboration.ConversationState> args)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnOfficeConversationStateChanged", this);

			try
			{
				// SAL
				EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "OnOfficeConversationStateChanged", args.State, this);

				// check terminating
				if (args.State == Microsoft.Rtc.Collaboration.ConversationState.Terminating)
				{
					// log
					EFS.EventLog.Log.LogInfo("Shutting down - office conversation is beyign terminated", this);

					// start 
					BeginShutdown();
				}
			}
			catch (System.Exception ex)
			{
				// handle ex
				HandleExceptionSafe(ex);
			}
		}

		/// <summary>
		/// Called when participant end point added
		/// </summary>
		protected override void ParticipantEndPointAdded()
		{
			// call base
			base.ParticipantEndPointAdded();

			// check if establish already triggered
			BeginStartDialPlayer();
		}

		#endregion

		#region Shutdown

		/// <summary>
		/// Cleanup
		/// </summary>
		protected override void CleanupSession()
		{
			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(CustomerSession.ConversationGuid, "CleanupSession", this); 
		}

		#endregion
	}
}

